package searchlogic

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"
)

// MindMapInfo 搜索用的导图信息
type MindMapInfo struct {
	Key                    string
	Title                  string
	Content                string
	Tags                   []string
	ContentFileFullPath    string
	ConfigJsonFileFullPath string
	ContentLastMod         time.Time
	ConfigJsonLastMod      time.Time
}

var currList = make([]MindMapInfo, 0, 64)
var allTags = make([]string, 0, 8)

type ConfigJson struct {
	Tags []string `json:"tags"`
}

func GetAllTags() []string {
	return allTags
}

func GetAllMaps() []MindMapInfo {
	return currList
}

func ReloadCache(mapsDir string) error {
	stat, err := os.Stat(mapsDir)
	if nil != err {
		return err
	}
	if !stat.IsDir() {
		return errors.New("指定的导图目录无效")
	}

	result := make([]MindMapInfo, 0, 64)
	tmpTags := make([]string, 0, 8)

	appendTags := func(newTags []string) {
		if nil == newTags || 0 == len(newTags) {
			return
		}
		for _, tag := range newTags {
			contains := false
			for _, tmpTag := range tmpTags {
				if strings.TrimSpace(strings.ToLower(tmpTag)) == strings.TrimSpace(strings.ToLower(tag)) {
					contains = true
					break
				}
			}
			if !contains {
				tmpTags = append(tmpTags, strings.TrimSpace(tag))
			}
		}
	}

	err = filepath.WalkDir(mapsDir, func(path string, d fs.DirEntry, err error) error {
		if nil != err {
			return err
		}
		if validBundleDir, mapInfo := isMapBundleDir(mapsDir, path, d); validBundleDir {
			// 文件内容填充
			// 把新结果与原来结果比较，如果修改时间未改变，则设置为原来记录的文本内容，而不用重新读取文件
			// 否则，需要重新读取
			tagsFilled := false
			contentFilled := false

			for _, oldMapInfo := range currList {
				if oldMapInfo.Key == mapInfo.Key {
					if oldMapInfo.ConfigJsonLastMod == mapInfo.ConfigJsonLastMod {
						mapInfo.Tags = oldMapInfo.Tags
						tagsFilled = true
						appendTags(mapInfo.Tags)
					}
					if oldMapInfo.ContentLastMod == mapInfo.ContentLastMod {
						mapInfo.Content = oldMapInfo.Content
						contentFilled = true
					}
				}
			}

			if !tagsFilled {
				configJson := ConfigJson{}
				bs, err := os.ReadFile(mapInfo.ConfigJsonFileFullPath)
				if nil != err {
					return err
				}
				err = json.Unmarshal(bs, &configJson)
				if nil != err {
					return err
				}
				mapInfo.Tags = configJson.Tags
				if nil == mapInfo.Tags {
					mapInfo.Tags = []string{}
				}
				appendTags(mapInfo.Tags)
			}
			if !contentFilled {
				bs, err := os.ReadFile(mapInfo.ContentFileFullPath)
				if nil != err {
					return err
				}
				mapInfo.Content = string(bs)
			}
			result = append(result, *mapInfo)
			return filepath.SkipDir
		}
		return nil
	})
	if nil != err {
		return err
	}

	// 结果处理，用新结果替换旧结果
	sort.SliceStable(result, func(i, j int) bool {
		if result[i].Key < result[j].Key {
			return true
		}
		return false
	})
	sort.SliceStable(tmpTags, func(i, j int) bool {
		if tmpTags[i] < tmpTags[j] {
			return true
		}
		return false
	})
	currList = result
	allTags = tmpTags
	return nil
}

func isMapBundleDir(mapsDir string, path string, d fs.DirEntry) (bool, *MindMapInfo) {
	if d.IsDir() && strings.HasSuffix(d.Name(), ".textbundle") {
		mdPath := filepath.Join(path, "text.md")
		configPath := filepath.Join(path, "info.json")
		mdSt, mdErr := os.Stat(mdPath)
		confSt, confErr := os.Stat(configPath)

		if nil == mdErr && nil == confErr && !mdSt.IsDir() && !confSt.IsDir() {
			title := strings.TrimSpace(path[len(mapsDir) : len(path)-len(".textbundle")])
			title = strings.ReplaceAll(title, "\\", "/")
			title = strings.TrimSuffix(strings.TrimPrefix(title, "/"), "/")
			title = strings.ReplaceAll(strings.TrimSpace(title), "/", " / ")

			lev := 0
			chars := []rune(title)
			for _, char := range chars {
				if '/' == char {
					lev++
				}
			}
			key := fmt.Sprintf("%02d%s", lev, title)

			return true, &MindMapInfo{
				ContentFileFullPath:    mdPath,
				ConfigJsonFileFullPath: configPath,
				ContentLastMod:         mdSt.ModTime(),
				ConfigJsonLastMod:      confSt.ModTime(),
				Title:                  title,
				Key:                    key,
			}
		}
	}
	return false, nil
}
